/***************************************************************
 * Name:      LuaInterface.cpp
 * Purpose:   Defines the LuaInterface functions
 * Author:    Milind Gupta (milind.gupta@gmail.com)
 * Website:   https://code.google.com/p/ganitsheets/
 * Created:   2011-03-05
 * Copyright: Milind Gupta ()
 * License:   Apache License 2.0
 **************************************************************/

#include "LuaInterface.h"

 // This function is used as a closure for each method/object exposed to Lua.
int LuaFunctionCall(lua_State *L)
{
    int objPointerIndex = lua_upvalueindex (1);  // Index for the light user data pointer
    int funcIdxIndex = lua_upvalueindex(2);      // Index for the function index value

    // Check if upvalue 1 is light user data
    // Check if upvalue 2 is a number
    if(lua_islightuserdata(L,objPointerIndex) && lua_isnumber(L,funcIdxIndex))
    {
        luaObj *pThis = (luaObj *) lua_touserdata (L, objPointerIndex);
        // Get the function index
        int funcIndex = (int) lua_tonumber (L, funcIdxIndex);

        int retValue = pThis->LuaScriptCall(funcIndex);
        return retValue;
    }
    else
        return 0;
}

// Function is used to run a specified Lua File and return any error messages if any
struct retPacket runLuaFile(std::string LuaFile)
{
    struct retPacket retMsg;
    // Read the Lua file here into a string and then execute the string
    int error;
    error = luaL_dofile(luaVM, LuaFile.c_str());
    if (error)
    {
        retMsg.returnCode = retPacket::ERR;
        retMsg.message = lua_tostring(luaVM, -1);
        retMsg.returnSubCode = 0;
        lua_pop(luaVM, 1);  /* pop error message from the stack */
    }
    return retMsg;
}

luaObj::luaAPIStruct luaObj::emptyLuaAPI[] = {{-1,"","",false,false,0}};


void luaObj::registerLuaStaticAPI(std::string hierPrefix)
{
    this->registerLuaAPITable(this->luaStaticAPI, LUA_GLOBALSINDEX, hierPrefix);
}

void luaObj::registerAPITableonGlobal(std::string tableName, std::string hierPrefix)
{
    this->returnAPITable(hierPrefix);
    // Now the API table is on the top of the stack
    // Place the table in the Globals table
    lua_setfield(luaVM, LUA_GLOBALSINDEX, tableName.c_str());
}

void luaObj::registerAPILudonGlobal(std::string ludName, std::string hierPrefix)
{
    this->returnAPILud(hierPrefix);
    // Now the API light user data is on the top of the stack
    // Place this light user data in the Globals table
    lua_setfield(luaVM, LUA_GLOBALSINDEX, ludName.c_str());
}

void luaObj::returnAPITable(std::string hierPrefix)
{
    // Create and push a lua table on the stack
    lua_newtable(luaVM);
    this->registerLuaAPITable(this->luaObjAPI, lua_gettop(luaVM), hierPrefix);
}

void luaObj::returnAPILud(std::string hierPrefix)
{
    // The index by which the API table will be stored in the registry
    lua_pushlightuserdata(luaVM,this);
    // Create the API in a table
    this->returnAPITable(hierPrefix);
    // Now prepare this table to be a metatable to the actual lightuserdata
    lua_pushvalue(luaVM,-1);
    lua_setfield(luaVM,-2,"__index");       // Set self as the __index field
    lua_pushboolean(luaVM,0);
    lua_setfield(luaVM,-2,"__metatable");   // Set the metatable field so the table cannot be modified in lua
    // Now set this as the metatable to the light user data
    lua_setmetatable(luaVM, -2);
    // Make a copy of the light user data
    lua_pushvalue(luaVM,-1);
    // Now place that meta table in the REGISTRY
    lua_getmetatable(luaVM,-1); // Get the meta table
    lua_settable(luaVM,LUA_REGISTRYINDEX);
    // Now we have the finished light user data on the top of the stack
}

void luaObj::registerLuaAPITable(const luaAPIStruct *luaAPI, int tableIndex, \
        std::string hierPrefix)
{
    // Register the Lua API for the object
    // Remember the Lua Stack index 1 is the element at the bottom i.e. which was pushed first
    //    and index -1 is the top element
    unsigned int i = 0;     // index for the luaAPI array
    int currTableIndex;     // index of the table on the stack in which the
                            // function or sub-table is to be created
    int stackTop = lua_gettop(luaVM);   // The top of the stack
    std::string hier, currTable;
    while(luaAPI[i].funcIndex!=-1)
    {
        currTableIndex = tableIndex;  // Start with the given table
        hier = luaAPI[i].tableHier;
        if(hierPrefix.length() > 0 && !(luaAPI[i].overRidePrefix))
        {
            if(hierPrefix.substr(hierPrefix.length()-1,1)==".")
                hier = hierPrefix + hier;
            else
                hier = hierPrefix + "." + hier;
        }
        while(hier!="")
        {
            currTable = hier.substr(0,hier.find("."));
            if(hier.find(".")==std::string::npos)
                hier = "";
            else
                hier = hier.substr(hier.find(".")+1,hier.length());
            // Now get currTable at the top of the Stack
            lua_pushstring(luaVM,currTable.c_str());
            lua_gettable(luaVM,currTableIndex);
            if(lua_isnil(luaVM,-1))
            {
                // currTable does not exist so create it this
                lua_pop(luaVM,1);   // Pop the nil value out
                lua_newtable(luaVM);
                lua_setfield(luaVM,currTableIndex,currTable.c_str());
                // Now get the table on to the stack
                lua_pushstring(luaVM,currTable.c_str());
                lua_gettable(luaVM,currTableIndex);
                // Now we have the table on the top of the stack
            }
            // Clear the rest of the stack
            if(lua_gettop(luaVM)>(stackTop + 1))
            {
                lua_replace(luaVM,stackTop + 1);
                lua_settop(luaVM,stackTop + 1);
            }
            currTableIndex = stackTop + 1;
        }
        // Now we have the table in which we need to add this function on top of the stack
        // Push the this pointer
        lua_pushlightuserdata (luaVM, this);
        // Push the function index
        lua_pushinteger(luaVM,lua_Integer(luaAPI[i].funcIndex));
        // Push the closure
        lua_pushcclosure(luaVM, LuaFunctionCall,2);
        lua_setfield(luaVM,currTableIndex,luaAPI[i].funcName.c_str());
        // Now increment the API index to register the next object
        i = i + 1;
        // Now remove the table from the stack
        lua_settop(luaVM,stackTop);
    }   // while(this->luaAPI[i].funcIndex!=-1)ends
}


